﻿/*
1. When a process p detects that the coordinator is not responding to requests, it initiates an election: 
 * a. p sends an election message to all processes. 
 * b. If nobody responds, then p wins and takes over.  
 * c. If one of the processes answers, then p 's job is done.  
2. If a process receives an election message from a lowernumbered process at any time, it: 
 * a. sends an SHUT message back. 
 * b. holds an election (unless its already holding one).  
3. A process announces its victory by sending all processes a message telling them that it is the new Leader.  
4. If a process that has been down recovers, it holds an election. 
*/


using System;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;


namespace network
{
    public class Election
    {
        public readonly ElectionInfo electionInfo;

        private IPAddress bindAddress;
        private Boolean receivedShut;
        private static int commPort = 3002;
        private UdpClient sender;

        private ASCIIEncoding encoder;
        private System.Diagnostics.EventLog logger;

        private void log(string str)
        {
            logger.Source = "Election";
            logger.WriteEntry(str);
        }

        public Election(System.Diagnostics.EventLog eventLogger, IPAddress interfaceIp)
        {
            logger = eventLogger;
            
            this.electionInfo = new ElectionInfo();

            this.encoder = new ASCIIEncoding();
            this.bindAddress = getInterface(interfaceIp);

            //this.sender = new UdpClient(AddressFamily.InterNetwork);
            Thread listener = new Thread(new ThreadStart(electionListener));
            listener.Start();
        }

        private IPAddress getInterface(IPAddress ip)
        {
            NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
            foreach (NetworkInterface ni in interfaces)
            {
                UnicastIPAddressInformationCollection ipColection = ni.GetIPProperties().UnicastAddresses;
                foreach (UnicastIPAddressInformation ipInfo in ipColection)
                {
                    if ((ipInfo.Address.AddressFamily == AddressFamily.InterNetwork) && (ipInfo.IPv4Mask != null))
                    {
                        bool equals = true;
                        byte[] ip1 = ipInfo.Address.GetAddressBytes();
                        byte[] ip2 = ip.GetAddressBytes();
                        byte[] mask = ipInfo.IPv4Mask.GetAddressBytes();

                        for (int i = 0; i < mask.Length; i++)
                        {
                            ip1[i] = (byte)(ip1[i] & mask[i]);
                            ip2[i] = (byte)(ip2[i] & mask[i]);
                            if (ip1[i] != ip2[i])
                            {
                                equals = false;
                            }
                        }
                        if (equals)
                        {
                            return ipInfo.Address;
                        }
                    }
                    else if (ipInfo.Address.AddressFamily == AddressFamily.InterNetworkV6)
                    {
                        // no support for IPv6 yet
                    }
                }
            }
            throw new NoInterface();
        }

        public void invokeElection() //Broadcast
        {
            byte[] outBuffer;
            this.electionInfo.beginElection();

            IPEndPoint bcRemoteEP = new IPEndPoint(IPAddress.Broadcast, commPort);
            outBuffer = encoder.GetBytes(String.Format("ELECTION {0}", electionInfo.myID));

            this.sender = new UdpClient(new IPEndPoint(this.bindAddress, 0));
            this.sender.Send(outBuffer, outBuffer.Length, bcRemoteEP); //sends from a random port to 3001

            this.receivedShut = false;
            Thread shutListener = new Thread(new ParameterizedThreadStart(listenShut));
            shutListener.Start(bcRemoteEP);

            Thread.Sleep(2000);
            if (!this.receivedShut)  // i proclame myself
            {
                shutListener.Abort();
                outBuffer = encoder.GetBytes(String.Format("LEADER {0}", electionInfo.myID));
                this.sender.Send(outBuffer, outBuffer.Length, bcRemoteEP);
            }
        }

        // private methods
        private void listenShut(object udpClient)  // listener thread
        {
            IPEndPoint client = (IPEndPoint)udpClient;
            byte[] rcvData = this.sender.Receive(ref client); //receives at port that had send the election msg
            MsgElectData msgData = GenericMethods.parseElectionMsg(rcvData);
            log(String.Format("Msg: \"{0}\" From: {1}", msgData.msgType, msgData.senderID));

            if (GenericMethods.equalMsg("SHUT", msgData.msgType))
            {
                this.receivedShut = true;
            }
            else
            {
                throw new UnexpectedMsg(msgData);
            }
        }

        private void sendShut(IPEndPoint receiverRemoteEP)
        {
            byte[] outBuffer = encoder.GetBytes(String.Format("SHUT {0}", electionInfo.myID));
            this.sender = new UdpClient(new IPEndPoint(this.bindAddress, 0));
            this.sender.Send(outBuffer, outBuffer.Length, receiverRemoteEP);    // send's the shut to the same port that the election msg was sent
        }
        private void electionListener()  // listener thread
        {
            byte[] rcvData = new byte[1024];
            IPEndPoint receiverRemoteEP = new IPEndPoint(IPAddress.Any, 0);
            UdpClient receiver = new UdpClient(commPort);
            receiver.DontFragment = true;

            while (true)
            {
                rcvData = receiver.Receive(ref receiverRemoteEP); //receives at port 3002
                MsgElectData msgData = GenericMethods.parseElectionMsg(rcvData);

                if (GenericMethods.equalMsg("ELECTION", msgData.msgType))
                {
                    if (electionInfo.myID > msgData.senderID) // leardership should be mine
                    {
                        this.sendShut(receiverRemoteEP);
                        if (!this.electionInfo.electionRunning)  // check if isn't holding an election already
                        {
                            this.invokeElection();
                        }
                    }
                }
                else if (GenericMethods.equalMsg("LEADER", msgData.msgType))
                {
                    if (this.electionInfo.myID > msgData.senderID)
                    {
                        this.sendShut(receiverRemoteEP);
                        if (!this.electionInfo.electionRunning)
                        {
                            this.invokeElection();
                        }
                    }
                    else
                    {
                        electionInfo.setLeader(msgData.senderID, receiverRemoteEP);
                        log(String.Format("Setting Leader to ID: {0}", msgData.senderID));
                    }
                }
                else if (GenericMethods.equalMsg("SOCORRO", msgData.msgType)) // message sent by the HPC Cluster Headnode when it gets no answer
                {
                    if (!this.electionInfo.electionRunning)  // check if isn't holding an election already
                    {
                        this.invokeElection();
                    }
                }
                else
                {
                    throw new UnexpectedMsg(msgData);
                }
            }
        }
    }
}